package com.sishu.njrtsdms.jooq.listener;

import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.now;

import com.sishu.njrtsdms.utils.SecurityUtils;

import org.jooq.Field;
import org.jooq.InsertQuery;
import org.jooq.QueryPart;
import org.jooq.SelectQuery;
import org.jooq.StoreQuery;
import org.jooq.Table;
import org.jooq.UpdateQuery;
import org.jooq.VisitContext;
import org.jooq.VisitListener;
import org.jooq.types.ULong;

import java.sql.Timestamp;
import java.util.HashSet;
import java.util.Set;

/**
 * 审计监听器
 * <p>
 * 用于执行插入和更新操作时,数据库更新对应的审计字段
 */
public class AuditVisitListener implements VisitListener {


    private static final Field<Timestamp> createdAt = field("create_date_time", Timestamp.class);
    private static final Field<ULong> createdBy = field("create_by", ULong.class);
    private static final Field<Timestamp> updatedAt = field("update_date_time", Timestamp.class);
    private static final Field<ULong> updatedBy = field("update_by", ULong.class);
    private final Set<Field<?>> auditFields = new HashSet<>();


    @Override
    public void visitStart(VisitContext context) {
//        if (SecurityUtils.getLoginUserOptional().isEmpty()) return;
        QueryPart topLevelQuery = context.context().topLevel();
        if (topLevelQuery instanceof SelectQuery<?>) {
            // No need to process further since we don't set audit fields for select queries
            return;
        }
        collectAuditFields(context.queryParts());
        if (topLevelQuery instanceof InsertQuery<?> insertQuery) {
            addAuditFieldsForInsert(insertQuery);
        }
        if (topLevelQuery instanceof UpdateQuery<?> updateQuery) {
            addAuditFieldsForUpdate(updateQuery);
        }
    }

    @Override
    public void visitEnd(VisitContext context) {
        // Without this line, audit fields are added
        // even if there is no audit fields in a table.
        auditFields.clear();
    }

    private void collectAuditFields(QueryPart[] queryParts) {
        for (QueryPart queryPart : queryParts) {
            if (queryPart instanceof Table<?> table) {
                if (table.field(createdAt) != null) {
                    auditFields.add(createdAt);
                }
                if (table.field(createdBy) != null) {
                    auditFields.add(createdBy);
                }
                if (table.field(updatedAt) != null) {
                    auditFields.add(updatedAt);
                }
                if (table.field(updatedBy) != null) {
                    auditFields.add(updatedBy);
                }
            }
        }
    }

    private void addAuditFieldsForInsert(StoreQuery<?> storeQuery) {
        if (auditFields.contains(createdAt)) {
            storeQuery.addValue(createdAt, now());
        }

        if (auditFields.contains(createdBy)) {
            storeQuery.addValue(createdBy, userId());
        }
//        addAuditFieldsForUpdate(storeQuery);
    }

    private void addAuditFieldsForUpdate(StoreQuery<?> storeQuery) {
        if (auditFields.contains(updatedAt)) {
            storeQuery.addValue(updatedAt, now());
        }
        if (auditFields.contains(updatedBy)) {
            storeQuery.addValue(updatedBy, userId());
        }
    }

    private ULong userId() {
        return SecurityUtils.getUserId();
    }

}
